#pragma once

#include "../DefinesCore.hpp"
#include <exception>
#ifdef CYBERTRON_LINUX
#include <string.h>
#include <stdexcept>
#endif
#include <stdexcept>
	
CYBERTRON_BEGIN

template <typename T, size_t MAXIMAL_ARRAY_SIZE = 32>
class StackArray
{
public:
	StackArray();
	StackArray(const StackArray& rhs);
	StackArray& operator = (const StackArray& rhs);
	~StackArray();

	void clear();

	inline T* find(const T& val) const;

	void push_back(T&& val);
	void push_back(const T& val);
	void erase(T* pElement);

	inline size_t size() const;
	inline T& operator[](size_t index);
	inline const T& operator[](size_t index) const;
	inline T* begin();
	inline T* end();
	inline const T* begin() const;
	inline const T* end() const;

private:
	void eraseImpl(size_t i);

private:
	size_t mSize;
	unsigned char mpData[MAXIMAL_ARRAY_SIZE * sizeof(T)];
};

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
StackArray<T, MAXIMAL_ARRAY_SIZE>::StackArray()
{
	mSize = 0;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
StackArray<T, MAXIMAL_ARRAY_SIZE>::StackArray(const StackArray& rhs)
{
	mSize = rhs.mSize;
	for (size_t i = 0; i < mSize; ++i)
	{
		T* pElement = (T*)(mpData + sizeof(T) * i);
		new(pElement) T(rhs[i]);
	}
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
StackArray<T, MAXIMAL_ARRAY_SIZE>& 
StackArray<T, MAXIMAL_ARRAY_SIZE>::operator = (const StackArray& rhs)
{
	clear();

	mSize = rhs.mSize;
	for (size_t i = 0; i < mSize; ++i)
	{
		T* pElement = (T*)(mpData + sizeof(T) * i);
		new(pElement) T(rhs[i]);
	}

	return *this;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
StackArray<T, MAXIMAL_ARRAY_SIZE>::~StackArray()
{
	clear();
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
void StackArray<T, MAXIMAL_ARRAY_SIZE>::clear()
{
	for (size_t i = 0; i < mSize; ++i)
	{
		T* pElement = (T*)(mpData + sizeof(T) * i);
		pElement->~T();
	}

	mSize = 0;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline T* StackArray<T, MAXIMAL_ARRAY_SIZE>::find(const T& val) const
{
	for (size_t i = 0; i < mSize; ++i)
	{
		T* pElement = (T*)(mpData + sizeof(T) * i);
		if (*pElement == val)
		{
			return pElement;
		}
	}

	return (T*)(mpData + sizeof(T) * mSize);
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
void StackArray<T, MAXIMAL_ARRAY_SIZE>::push_back(T&& val)
{
	if (mSize == MAXIMAL_ARRAY_SIZE)
	{
        throw std::runtime_error("Stack Array exceeded maximal size!");
	}

	T* pElement = (T*)(mpData + sizeof(T) * mSize);
	new(pElement) T(std::move(val));

	mSize++;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
void StackArray<T, MAXIMAL_ARRAY_SIZE>::push_back(const T& val)
{
	if (mSize == MAXIMAL_ARRAY_SIZE)
	{
        throw std::runtime_error("Stack Array exceeded maximal size!");
	}

	T* pElement = (T*)(mpData + sizeof(T) * mSize);
	new(pElement) T(val);

	mSize++;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
void StackArray<T, MAXIMAL_ARRAY_SIZE>::eraseImpl(size_t i)
{
	if (i >= mSize)
	{
		return;
	}

	T* pElement = (T*)(mpData + sizeof(T) * i);
	pElement->~T();
	memmove(pElement, pElement + 1, (mSize-i)*sizeof(T));
	--mSize;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
void StackArray<T, MAXIMAL_ARRAY_SIZE>::erase(T* pElement)
{
	size_t i = pElement - begin();
	eraseImpl(i);
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline size_t StackArray<T, MAXIMAL_ARRAY_SIZE>::size() const
{
	return mSize;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline T& StackArray<T, MAXIMAL_ARRAY_SIZE>::operator[](size_t index)
{
	T* pElement = (T*)(mpData + sizeof(T) * index);
	return *pElement;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline const T& StackArray<T, MAXIMAL_ARRAY_SIZE>::operator[](size_t index) const
{
	const T* pElement = (const T*)(mpData + sizeof(T) * index);
	return *pElement;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline T* StackArray<T, MAXIMAL_ARRAY_SIZE>::begin()
{
	T* pElement = (T*)(mpData + sizeof(T) * 0);
	return pElement;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline T* StackArray<T, MAXIMAL_ARRAY_SIZE>::end()
{
	T* pElement = (T*)(mpData + sizeof(T) * mSize);
	return pElement;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline const T* StackArray<T, MAXIMAL_ARRAY_SIZE>::begin() const
{
	const T* pElement = (const T*)(mpData + sizeof(T) * 0);
	return pElement;
}

template <typename T, size_t MAXIMAL_ARRAY_SIZE>
inline const T* StackArray<T, MAXIMAL_ARRAY_SIZE>::end() const
{
	const T* pElement = (const T*)(mpData + sizeof(T) * mSize);
	return pElement;
}

CYBERTRON_END
